// Copyright (c) 2025 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once


#include <Arduino.h>
#include <SPI.h>


#if defined(__AVR_ARCH__)

// AVR 平台下，官方SPI 库里SPISettings 的定义方式难以兼容

    #define _SOFT__SPI3__SPI_SETTINGS_COMPATIBLE 0

#elif defined(STM32_CORE_VERSION)

    #define _SOFT__SPI3__SPI_SETTINGS_COMPATIBLE 1

#else

    #define _SOFT__SPI3__SPI_SETTINGS_COMPATIBLE 0

#endif


namespace soft_spi3 {

#if _SOFT__SPI3__SPI_SETTINGS_COMPATIBLE == 1

    namespace hide {
        /**
         * @brief SPISettings 类把配置变量都设为private，只允许SPI 类读取，所以要兼容SPISettings，只能用比较脏的手段
         *
         */
        union SPISettingsHelper {
            SPISettings spi_settings{};

            struct SPIConfig {
                uint32_t clockFreq;  // specifies the spi bus maximum clock speed
                BitOrder bitOrder;   // bit order (MSBFirst or LSBFirst)
                SPIMode dataMode;    // one of the data mode
            } config;
        };


        static_assert(sizeof(SPISettings) == sizeof(SPISettingsHelper));
    }  // namespace hide

#endif


#if _SOFT__SPI3__SPI_SETTINGS_COMPATIBLE > 0

    struct SoftSPISettings {
        uint32_t clockFreq;
        uint8_t bitOrder;
        uint8_t dataMode;

        // 如果可以兼容SPISettings，就给SoftSPISettings 定义隐式转换函数
        SoftSPISettings(const SPISettings &settings) {
            auto *h = reinterpret_cast<const hide::SPISettingsHelper *>(&settings);
            clockFreq = h->config.clockFreq;
            bitOrder = h->config.bitOrder;
            dataMode = h->config.dataMode;
        }


        SoftSPISettings(uint32_t clk, uint8_t bit_order, uint8_t mode) :
            clockFreq(clk),
            bitOrder(bit_order),
            dataMode(mode) {}
    };

#else

    struct SoftSPISettings {
        uint32_t clockFreq;
        uint8_t bitOrder;
        uint8_t dataMode;
    };

#endif


    /**
     * @brief 反转1 字节位序
     *
     * @param b
     * @return constexpr uint8_t
     */
    constexpr uint8_t reverse_u8(uint8_t b) {
        // FROM:
        // https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte
        // LICENSE:
        // CC BY-SA 4.0
        b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
        b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
        b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
        return b;
    }


    constexpr uint16_t reverse_u16(uint16_t b) {
        uint8_t h = b >> 8;
        uint8_t l = b & 0xff;

        h = reverse_u8(h);
        l = reverse_u8(l);
        return (static_cast<uint16_t>(l) << 8) + h;
    }


#ifdef STM32_CORE_VERSION

    class FastSpiPin {
       private:
        GPIO_TypeDef *_port;
        uint32_t _pin;

       public:
        FastSpiPin(uint8_t pin_num, bool is_output) {
            auto pn = digitalPinToPinName(pin_num);
            if (pn == NC) {
                // pin_num is not a valid pin,
                // causing HardFault if used
                _port = reinterpret_cast<GPIO_TypeDef *>(0xFFFFFFF3);  // 把指针设为非对齐的无效地址
                _pin = 0;
                return;
            }

            _port = get_GPIO_Port(STM_PORT(pn));
            _pin = STM_LL_GPIO_PIN(pn);
        }

        void set(bool b) const {
            digital_io_write(_port, _pin, b);
        }

        auto get() const {
            return digital_io_read(_port, _pin);
        }
    };

#elif defined(__AVR_ARCH__)

    class FastSpiPin {
       private:
        using PortType = volatile uint8_t *;

        PortType _port;
        uint8_t _pin_mask;

       public:
        FastSpiPin(uint8_t pin_num, bool is_output) {
            _pin_mask = digitalPinToBitMask(pin_num);
            uint8_t port_num = digitalPinToPort(pin_num);
            if (is_output) {
                _port = portOutputRegister(port_num);
            }
            else {
                _port = portInputRegister(port_num);
            }
        }

        void set(bool b) const {
            if (b) {
                *_port |= _pin_mask;
            }
            else {
                *_port &= ~_pin_mask;
            }
        }

        auto get() const {
            return (*_port & _pin_mask);
        }
    };

#else

    // 没有快速方案，用默认的digitalWrite 和digitalRead
    class FastSpiPin {
       private:
        uint_fast8_t _pin;

       public:
        FastSpiPin(uint8_t pin_num, bool is_output) :
            _pin(pin_num) {}

        void set(bool b) const {
            digitalWrite(_pin, b);
        }

        auto get() const {
            return digitalRead(_pin);
        }
    };

#endif


    constexpr auto SPI_MODE0_LOW_RISE = SPI_MODE0;   // CPOL = 0, CPHA = 0
    constexpr auto SPI_MODE1_LOW_FALL = SPI_MODE1;   // CPOL = 0, CPHA = 1
    constexpr auto SPI_MODE2_HIGH_FALL = SPI_MODE2;  // CPOL = 1, CPHA = 0
    constexpr auto SPI_MODE3_HIGH_RISE = SPI_MODE3;  // CPOL = 1, CPHA = 1


    class SoftSPI3 {
       private:
        FastSpiPin _mosi;
        FastSpiPin _miso;
        FastSpiPin _sck;

        uint8_t _mosi_num;
        uint8_t _miso_num;
        uint8_t _sck_num;

        uint8_t _hold;  // 控制电平的保持时间，也就是控制频率

        bool _no_miso = false;
        bool _msb_first = true;
        bool _idle_high = false;           // CPOL
        bool _sample_edge_second = false;  // CPHA


        void hold_clk() {
            // TODO
        }


        void set_sck(bool b) {
            _sck.set(b);
        }


        void set_sck_inactive() {
            if (_idle_high) {
                _sck.set(1);  // SCK is high when idle, so set it high to inactive
            }
            else {
                _sck.set(0);  // SCK is low when idle, so set it low to inactive
            }
        }


        void set_mosi(bool b) {
            _mosi.set(b);
        }

        auto get_miso() {
            return _miso.get();
        }


        /**
         * @brief 按照MSB first 的顺序收发一位数据
         *
         * 在MOSI 上发送data 的MSB；接受MISO 的电平，放在返回值的LSB 上。
         *
         * @param data
         * @return T
         */
        template <typename T>
        T transfer_msb(T data) {
            T so = 0;
            bool l = _idle_high;

            if (_sample_edge_second) {
                // 第一边沿
                // 如果在第二边沿采样，MOSI 电平在第一边沿后设置
                l = !l;
                set_sck(l);
                hold_clk();
            }

            constexpr T MSB_MASK = 0x01 << (sizeof(T) * 8 - 1);

            // MOSI
            set_mosi(data & MSB_MASK);
            hold_clk();

            // 第二边沿，或第一边沿
            // 如果在第一边沿采样，先设置电平，再触发边沿
            l = !l;
            set_sck(l);

            // MISO
            if (!_no_miso && get_miso()) {
                so |= 0x01;
            }

            hold_clk();

            if (!_sample_edge_second) {
                // 第二边沿
                l = !l;
                set_sck(l);
            }

            return so;
        }


        void set_data_mode(uint8_t m) {
            switch (m) {
                case SPI_MODE0:
                    _idle_high = false;
                    _sample_edge_second = false;
                    break;

                case SPI_MODE1:
                    _idle_high = false;
                    _sample_edge_second = true;
                    break;

                case SPI_MODE2:
                    _idle_high = true;
                    _sample_edge_second = false;
                    break;

                case SPI_MODE3:
                    _idle_high = true;
                    _sample_edge_second = true;
                    break;
            }
        }


        void set_bit_order(uint8_t odr) {
            if (odr == MSBFIRST) {
                _msb_first = true;
            }
            else {
                _msb_first = false;
            }
        }


       public:
        SoftSPI3(uint8_t mosi, uint8_t miso, uint8_t sck) :
            _mosi(mosi, true),
            _miso(miso, false),
            _sck(sck, true),
            _mosi_num(mosi),
            _miso_num(miso),
            _sck_num(sck) {}


        SoftSPI3(uint8_t mosi, uint8_t sck) :
            SoftSPI3(mosi, 0, sck) {
            // 可以不设置MISO 引脚，仅输出
            _no_miso = true;
        }


#ifdef OUTPUT_OPEN_DRAIN

        /**
         * @brief 配置GPIO 模式，准备输出和接收信号。
         *
         * 或许会有使用开漏输出的需求，所以加上这个参数。
         *
         * @param use_open_drain
         */
        void begin(bool use_open_drain = false) {
            if (!_no_miso) {
                pinMode(_miso_num, INPUT_PULLUP);
            }

            auto m = OUTPUT;
            if (use_open_drain) {
                m = OUTPUT_OPEN_DRAIN;
            }

            pinMode(_sck_num, m);
            pinMode(_mosi_num, m);
        }

#else

        void begin() {
            if (!_no_miso) {
                pinMode(_miso_num, INPUT_PULLUP);
            }

            pinMode(_sck_num, OUTPUT);
            pinMode(_mosi_num, OUTPUT);
        }

#endif


        void end() {
            if (!_no_miso) {
                pinMode(_miso_num, INPUT);
            }

            pinMode(_sck_num, INPUT);
            pinMode(_mosi_num, INPUT);
        }


        void beginTransaction(const SoftSPISettings &settings) {
            // TODO: 频率
            set_bit_order(settings.bitOrder);
            set_data_mode(settings.dataMode);
            set_sck_inactive();  // 设置SCK 为空闲状态
        }


        void endTransaction(void) {}


        uint8_t transfer(uint8_t data) {
            if (!_msb_first) {
                data = reverse_u8(data);
            }

            uint8_t so = 0;

            for (auto i = 8; i > 0; --i) {
                so <<= 1;
                so |= transfer_msb(data);
                data <<= 1;
            }

            if (_no_miso || _msb_first) {
                return so;
            }
            else {
                so = reverse_u8(so);
                return so;
            }
        }


        uint16_t transfer16(uint16_t data) {
            if (!_msb_first) {
                data = reverse_u16(data);
            }

            uint16_t so = 0;

            for (auto i = 16; i > 0; --i) {
                so <<= 1;
                so |= transfer_msb(data);
                data <<= 1;
            }

            if (_no_miso || _msb_first) {
                return so;
            }
            else {
                so = reverse_u16(so);
                return so;
            }
        }


        uint16_t transferBits(uint16_t data, uint8_t bits) {
            if (bits >= 16) {
                return transfer16(data);
            }

            if (_msb_first) {
                data <<= (16 - bits);
            }
            else {
                data = reverse_u16(data);
            }

            uint16_t so = 0;

            for (auto i = bits; i > 0; --i) {
                so <<= 1;
                so |= transfer_msb(data);
                data <<= 1;
            }

            if (_no_miso || _msb_first) {
                so >>= (16 - bits);
                return so;
            }
            else {
                so = reverse_u16(so);
                return so;
            }
        }
    };

}  // namespace soft_spi3


// Export
using SoftSPI3 = soft_spi3::SoftSPI3;
